﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Windows;
using System.Windows.Resources;
using System.Xml.Linq;
using System.Net.NetworkInformation;
using OpenLightGroup.Common;

namespace OpenLightGroup.Prism
{
    /// <summary>
    /// 
    /// </summary>
    /// <remarks>
    /// http://www.wintellect.com/CS/blogs/jlikness/archive/tags/out+of+browser/default.aspx
    /// </remarks>
    /// 
    [Export]
    public class CachingDeploymentCatalog : ComposablePartCatalog
    {
        public event EventHandler<AsyncCompletedEventArgs> DownloadCompleted;
        public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;

        protected static readonly List<string> _parts = new List<string>();
        protected readonly List<Assembly> _assemblies = new List<Assembly>();
        protected readonly AggregateCatalog _typeCatalogs = new AggregateCatalog();

        #region Ctor

        public CachingDeploymentCatalog(string uri)
        {
            Uri = new Uri(uri, UriKind.Relative);
        }

        public CachingDeploymentCatalog(Uri uri)
        {
            Uri = uri;
        }

        #endregion

        public override IQueryable<ComposablePartDefinition> Parts
        {
            get
            {
                return _typeCatalogs.Parts;
            }
        }

        public Uri Uri { get; set; }

        public void DownloadAsync()
        {
            if (NetworkInterface.GetIsNetworkAvailable())
            {
                Debug.WriteLine("Begin async download of XAP {0}", Uri);
                var webClient = new WebClient();
                webClient.OpenReadCompleted += OnWebClientOpenReadCompleted;
                webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnWebClientDownloadProgressChanged);
                webClient.OpenReadAsync(new Uri(WebSource.GetSourceDirectory(), Uri));
            }
            else
            {
                ReadFromIso();
            }
        }

        protected virtual void OnWebClientDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            if (this.DownloadProgressChanged != null)
                this.DownloadProgressChanged(this, e);
        }

        protected virtual void OnWebClientOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {

            if (e.Error != null)
            {
                // will try to read from ISO as a fallback 
                Debug.WriteLine("Catalog load failed: {0}", e.Error.Message);
            }
            else
            {
                Debug.WriteLine("Download of xap {0} completed.", Uri);

                var isoName = AsFileName();

                Debug.WriteLine("Attempting to store XAP {0} to local file {1}", Uri, isoName);

                try
                {
                    using (var iso = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        using (var br = new BinaryReader(e.Result))
                        {
                            using (var bw = new BinaryWriter(iso.OpenFile(isoName, FileMode.Create, FileAccess.Write)))
                            {
                                bw.Write(br.ReadBytes((int)e.Result.Length));
                            }
                        }
                    }
                }
                catch (Exception ex)
                {

                    Debug.WriteLine("Failed to store XAP {0} to local file {1}", Uri, isoName);
                    Debug.WriteLine(ex.Message);

                }
            }

            ReadFromIso();
        }

        protected virtual string AsFileName()
        {
            return Uri.ToString().Replace(':', '.').Replace('/', '.');
        }

        protected virtual void ReadFromIso()
        {
            Debug.WriteLine("Attempting to retrieve XAP {0} from isolated storage.", Uri);

            using (var iso = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (iso.FileExists(AsFileName()))
                {
                    ProcessXap(iso.OpenFile(AsFileName(), FileMode.Open, FileAccess.Read));
                }
                else
                {
                    if (DownloadCompleted != null)
                    {
                        DownloadCompleted(this, new AsyncCompletedEventArgs(
                                                    new Exception(
                                                        string.Format(
                                                            "The requested XAP was not found in isolated storage: {0}",
                                                            Uri)), false, null));
                    }
                }
            }
        }

        protected virtual void ProcessXap(Stream stream)
        {
            var manifestStr = new
                StreamReader(
                Application.GetResourceStream(new StreamResourceInfo(stream, null),
                                                new Uri("AppManifest.xaml", UriKind.Relative))
                    .Stream).ReadToEnd();

            var deploymentRoot = XDocument.Parse(manifestStr).Root;

            if (deploymentRoot == null)
            {
                Debug.WriteLine("Unable to find manifest for XAP {0}", Uri);
                if (DownloadCompleted != null)
                {
                    DownloadCompleted(this,
                                        new AsyncCompletedEventArgs(new Exception("Could not find manifest root in XAP"),
                                                                    false, null));
                }
                return;
            }

            var parts = (from p in deploymentRoot.Elements().Elements() select p).ToList();

            foreach (var src in
                from part in parts
                select part.Attribute("Source")
                    into srcAttr
                    where srcAttr != null
                    select srcAttr.Value)
            {
                ProcessPart(src, stream);
            }

            foreach (var assembly in _assemblies)
            {
                try
                {
                    _typeCatalogs.Catalogs.Add(new TypeCatalog(assembly.GetTypes()));
                }
                catch (ReflectionTypeLoadException ex)
                {
                    Debug.WriteLine("Exception encountered loading types: {0}", ex.Message);

                    if (Debugger.IsAttached)
                    {
                        foreach (var item in ex.LoaderExceptions)
                        {
                            Debug.WriteLine("With exception: {0}", item.Message);
                        }
                    }

                    throw;
                }
            }
            Debug.WriteLine("Xap file {0} successfully loaded and processed.", Uri);

            if (DownloadCompleted != null)
            {
                DownloadCompleted(this, new AsyncCompletedEventArgs(null, false, null));
            }

        }

        protected virtual void ProcessPart(string src, Stream stream)
        {
            Debug.WriteLine("Network aware catalog is parsing assembly part {0}", src);

            var assemblyPart = new AssemblyPart();

            var srcInfo = Application.GetResourceStream(new StreamResourceInfo(stream, "application/binary"),
                                                        new Uri(src, UriKind.Relative));

            lock (((ICollection)_parts).SyncRoot)
            {
                if (_parts.Contains(src))
                {
                    return;
                }

                _parts.Add(src);

                if (src.EndsWith(".dll"))
                {
                    var assembly = assemblyPart.Load(srcInfo.Stream);
                    _assemblies.Add(assembly);
                }
                else
                {
                    assemblyPart.Load(srcInfo.Stream);
                }
            }
        }
    }
}
